<template>
    <div class="micro-app-component-wrapper">
        <!-- 
            这里做一个 props 传递说明：props 是向微应用传递数据的入口，这个数据相当于一个缓存
                如果应用是重建模式，那么每次重建都能获取到最新的
                如果是保活，则只有第一次创建时，能够获取到
            这里的数据分为两种
                - 微应用初始化的系统数据
                - 用户使用远程组件时，传递的业务数据
            所以这里做了一个区分，把系统关键数据传递的同时，以 remoteData 字段传递业务数据，供用户获取
            childrenLoaded 则是一个反向通讯机制，由子抛出，当前容器响应
         -->

        <WujieVue
            v-if="appUrl"
            :name="microAppName"
            :url="targetPath"
            :plugins="[
                {
                    jsBeforeLoaders: [...microJsSourceMapping, ...globalVariableMappingScripts],
                    cssBeforeLoaders: [...microCssSourceMapping]
                },
                { jsIgnores: [...microJsIgnores] },
                { jsExcludes: [/\/qv/] },
                { cssIgnores: [/vue-office/, /basic/] }
            ]"
            :props="{ ...microData, remoteData: data, childrenLoaded: childrenLoaded }"
            :attrs="iframeAttrs"
            :before-load="handleBeforeLoad"
            :before-mount="handleBeforeMount"
            :after-mount="handleAfterMount"
            :before-unmount="handleBeforeUnmount"
            :after-unmount="handleAfterUnmount"
            :activated="handleActivated"
            :deactivated="handleDeactivated"
            :load-error="handleLoadError"
        />

        <div v-if="loadError" class="micro-error-wrapper">
            <MicroAppErr :app-name="`Route Path：${componentUrl} `" />
        </div>

        <g-loding-shade :show="isLoading" />
    </div>
</template>

<script lang="ts">
import { defineComponent } from 'vue'
export default defineComponent({
    name: 'WujieRemoteMicroAppComponent'
})
</script>

<script lang="ts" setup>
import { watch, ref, computed, onBeforeUnmount } from 'vue'
import WujieVue from 'wujie-vue3'
import _ from 'lodash'
import { v4 as uuidv4 } from 'uuid'
import {
    interceptorsReqHandle,
    interceptorsResHandle
} from '@jsjn/micro-core-micro-main/httpInterceptors'
import SendMicroAppData from '../interface/SendMicroAppData'
import { routeMode } from '@jsjn/micro-core-micro-main/routeMode'
import { useStore } from '@jsjn/micro-core-store'
import BUS_EVENT_KEYS from '@jsjn/micro-core-micro-main/wujie/constants/BUS_EVENT_KEYS'
import MicroAppErr from '@jsjn/micro-core-views/error/microAppErr.vue'
import { global } from '@jsjn/utils'

const { bus, setupApp, preloadApp, destroyApp } = WujieVue

const props = withDefaults(
    defineProps<{
        /**
         * 微应用的地址，仅做资源加载的地址
         */
        appUrl: string
        /**
         * 微应用内注册的组件的路由地址
         */
        componentUrl: string
        /**
         * 传递给目标路由组件的数据（path + querty）
         */
        data?: object
        /**
         * 数据同步模式，无界框架新增模式
         */
        dataSync?: boolean
    }>(),
    {
        appUrl: '',
        componentUrl: '',
        data: null,
        dataSync: false
    }
)

const emits = defineEmits(['update:data', 'datachange'])
const store = useStore()

const isLoading = ref<boolean>(false)
const loadError = ref<boolean>(!props.appUrl)

/**
 * 应用的 name 变得不再重要，保证唯一即可
 */
const microAppName = `randomid${uuidv4().replace(/-/g, '')}`

/**
 * TODO: 通过 url 的变化，达成微应用的重新加载
 * 这里是为了适配历史组件的 url 取参，将用户传递的 data 已 url 的参数传递给子应用，子应用内部拥有虚拟路由，依然可以通过 route.query 获取到参数
 * 并且无界区别于 micro-app url 变化后，会主动刷新（重建）微应用，能够达到参数的动态刷新
 */
const targetPath = computed(
    () =>
        `${props.appUrl}/#/${microAppName}${props.componentUrl}${
            !props.dataSync ? (props.data ? '?' + getParams(props.data) : '') : ''
        }`
)

/**
 * 基座加载微应用资源规则透传
 */
const microJsSourceMapping = window['microJsSourceMapping']
const microCssSourceMapping = window['microCssSourceMapping']
const globalVariableMappingScripts = window['globalVariableMappingScripts']
const microJsIgnores = window['microJsIgnores']
const iframeAttrs = window['iframeAttrs']

// 数据透传
const microData = computed<SendMicroAppData>(() => {
    const copyState = _.cloneDeep(store.state)

    /**
     * 作为嵌套微应用，应该保证基座应用的 registeredRoutes 被正确传递
     * 因为在微应用接收的时候，将基座应用的 注册路由作为 remoteRegisteredRoutes 存储
     * 但是传递给嵌套子应用的实际为 registeredRoutes
     * 这边进行二者的同化即可
     */
    copyState.appFuncTree.registeredRoutes = copyState.appFuncTree.remoteRegisteredRoutes

    return {
        vuex: copyState,
        interceptorsReqHandle,
        interceptorsResHandle,
        routeMode
    }
})

/**
 * 微应用启动数据广播
 */
watch(
    () => microData.value,
    _.debounce(() => {
        bus.$emit(BUS_EVENT_KEYS.REMOTE_COMPONENT_VIEW_UPDATE_PROPS, _.cloneDeep(microData.value))
    }, 300),
    { deep: true }
)

if (props.dataSync) {
    /**
     * 单播：发送的 key
     */
    const dispatchDataEKey = `REMOTE_COMPONENT_BUSINESS_PROPS_CHANGE_${microAppName}`
    /**
     * 单播：接收的 key
     */
    const receptionDataEKey = `REMOTE_COMPONENT_VIEW_DATA_UPDATE_${microAppName}`

    /**
     * 业务数据变化针对性单播
     */
    watch(
        () => props.data,
        (data) => {
            bus.$emit(dispatchDataEKey, data)
        },
        { deep: true, immediate: true }
    )

    /**
     * 接收子组件的抛出
     */
    function _updateParentDataHandle(data: any) {
        emits('update:data', data)
    }
    bus.$on(receptionDataEKey, _updateParentDataHandle)
    onBeforeUnmount(() => {
        bus.$off(receptionDataEKey, _updateParentDataHandle)
    })
}

// 接收数据并抛出，微应用交互转换为 vue 组件间的交互
function emitDataChange(data: object) {
    emits('datachange', data)
}
bus.$on(`MICRO_REMOTE_DATA_CHANGE_${microAppName}`, emitDataChange)
onBeforeUnmount(() => {
    bus.$off(`MICRO_REMOTE_DATA_CHANGE_${microAppName}`, emitDataChange)
})

// 生命周期
const handleBeforeLoad = (chidrenWin: Window) => {
    // 为微应用添加远程组件环境标识
    chidrenWin['__IS_REMOTE_COMPONENT__'] = true
    isLoading.value = true

    // console.log(`%c 1. handleBeforeLoad **************** `, 'color: #67c23a;', +new Date())
}

// 子应用模拟挂载完毕
const childrenLoaded = () => {
    isLoading.value = false
}

const handleActivated = (chidrenWin: Window) => {
    // console.log(`%c 6. handleActivated **************** `, 'color: #67c23a;', +new Date())
}
const handleDeactivated = (chidrenWin: Window) => {
    // console.log(`%c 7. handleDeactivated **************** `, 'color: #67c23a;', +new Date())
}

const handleLoadError = () => {
    isLoading.value = false
    loadError.value = true
    // console.log(`%c 8. handleLoadError **************** `, 'color: #67c23a;', +new Date())
}

const handleBeforeMount = () => {
    // console.log(`%c 2. handleBeforeMount **************** `, 'color: #67c23a;', +new Date())
}
const handleAfterMount = () => {
    // console.log(`%c 3. handleAfterMount **************** `, 'color: #67c23a;', +new Date())
}
const handleBeforeUnmount = () => {
    // console.log(`%c 4. handleBeforeUnmount **************** `, 'color: #67c23a;', +new Date())
}
const handleAfterUnmount = () => {
    // console.log(`%c 5. handleAfterUnmount **************** `, 'color: #67c23a;', +new Date())
}

// 容错处理：基座应用尝试加载微应用资源，加载出错，则显示错误页
watch(
    () => props.appUrl,
    (url) => {
        if (!url) {
            loadError.value = true
            isLoading.value = false
            return
        }

        fetch(`${url}/index.html`)
            .then((res) => {})
            .catch((err) => {
                loadError.value = true
                isLoading.value = false
            })
    },
    { immediate: true }
)

//用&拼接对象成字符串
function getParams(params) {
    let paramStr = ''
    Object.keys(params).forEach((item) => {
        if (paramStr === '') {
            paramStr = `${item}=${params[item]}`
        } else {
            paramStr = `${paramStr}&${item}=${params[item]}`
        }
    })
    return paramStr
}
</script>

<style lang="scss" scoped>
.micro-app-component-wrapper {
    position: relative;
    width: 100%;
    // height: 100%;
    // overflow: hidden;
    min-height: 120px;

    > div {
        width: 100%;
        height: 100%;
    }

    .micro-error-wrapper {
        position: absolute;
        top: 0;
        left: 0;
        padding: 2px;
        background: #fff;
        display: flex;
        align-items: center;
        justify-content: center;

        :deep(h1) {
            margin: 0;
        }
    }
}
</style>
